﻿using System;
using System.Reflection;
using System.Text.RegularExpressions;

namespace Paragon
{
    /// <summary>
    /// Enhancing the basic string functionality
    /// </summary>
    /// <remarks>
    /// </remarks>
    /// <example>
    ///   <code>
    ///     string template = "ID: {${p0:Id}} Has ${p1:Name} for ${p1:Values.Count} Values";
    ///     Console.WriteLine(template.Para(a, a.B);
    ///   </code>
    /// </example>
    public static class StringExtensions
    {
        /// <summary>
        /// This is the regular expression used to parse the template for the Para function.
        /// </summary>
        /// <remarks>
        /// It looks for the expression starting with ${ and ending in }.
        /// 
        /// I do not remember what r and h are for.  It might have been for Header and Request parameters, but I do not know if I want to implement that here.
        /// </remarks>
        /// <example>
        ///   ${p0:Count:######}
        ///   ${e:ComputerName}
        /// </example>
        public static Regex parameterEx = new Regex("\\$\\{([e|p[\\d]+|h|r]):?([\\w\\d\\.]*):?([\\d\\w\\s\\.\\\\:\\-/]*)\\}", RegexOptions.IgnoreCase);

        /// <summary>
        /// Extends the string object to allow it do more advanced templates.
        /// </summary>
        /// <remarks>
        /// Expressions starts with ${.  They are then followed with the letter indicating the source.
        /// In the case of a parameter, it is followed by the number of the parameter.  The specific
        /// property used is separated by a ':'.  In the case of an environment variable (e), the
        /// environment variable name would follow.  In the case of a parameter (p#), use the property
        /// name you wish to be displayed.  You can even use children properties, i.e. ${p0:Name.Length}.
        /// Next the string format parameter follows.  For example, if you wanted it to be formated as
        /// currency, use the 'c', i.e. ${p1:UnitPrice.Value:c}.  It uses the same formatting parameters
        /// as the string.Format function, but you do not have to specify the index.
        /// </remarks>
        /// <param name="template">A string to be formatted with ${} expression.</param>
        /// <param name="parameters">Parameters that can be referenced in the template via the ${} expressions</param>
        /// <returns></returns>
        public static string Para(this string template, params object[] parameters)
        {
            string result = template;

            MatchCollection matches = parameterEx.Matches(result);

            foreach (Match match in matches)
            {
                string parameterType = match.Groups[1].Value;
                string parameterName = match.Groups[2].Value;
                string parameterFormat = null;
                object parameterValue = string.Empty;

                if (match.Groups.Count >= 4)
                    parameterFormat = match.Groups[3].Value;

                // Determines the parameter type from the first letter after the ${ in the template
                switch (parameterType.ToUpper()[0])
                {
                    // Environment variable
                    case 'E':
                        parameterValue = GetEnvironmentVariable(parameterName);
                        break;
                    // Parameter variable
                    case 'P':
                        string strParameterIndex = parameterType.Substring(1);
                        int parameterIndex = Int32.Parse(strParameterIndex);

                        if (!string.IsNullOrEmpty(parameterName))
                            parameterValue = GetValue(parameters[parameterIndex], parameterName);
                        else
                            parameterValue = parameters[parameterIndex];
                        break;
                }

                if (parameterValue == null)
                    parameterValue = string.Empty;

                result = result.Replace(match.Groups[0].Value,
                    (string.IsNullOrEmpty(parameterFormat) ? parameterValue.ToString() : string.Format("{0:" + parameterFormat + "}", parameterValue)));
            }

            return result;
        }

        private static object GetEnvironmentVariable(string parameterName)
        {
            object result = null;
            PropertyInfo property = typeof(Environment).GetProperty(parameterName);

            if (property != null)
            {
                result = property.GetValue(null, null);
            }

            if (property == null)
            {
                result = Environment.GetEnvironmentVariable(parameterName);
            }

            return result;
        }

        private static object GetValue(object value, string propertyName)
        {
            try
            {
                if (propertyName.Contains("."))
                {
                    int index = propertyName.IndexOf('.');
                    string parentProperty = propertyName.Substring(0, index);
                    string childProperty = propertyName.Substring(index + 1, propertyName.Length - index - 1);

                    object parent = GetValue(value, parentProperty);
                    return GetValue(parent, childProperty);
                }
                else
                {
                    foreach (PropertyInfo property in value.GetType().GetProperties())
                    {
                        if (property.CanRead && string.Equals(property.Name, propertyName, StringComparison.CurrentCultureIgnoreCase))
                        {
                            return property.GetValue(value, null);
                        }
                    }
                }
            }
            catch
            {
            }

            return null;

        }
    }
}
